package cn.foolishbird.crow.encrypt.springmvc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.List;

import static cn.foolishbird.crow.encrypt.springmvc.RSAUtils.RSA_KEY_ALGORITHM;

/**
 * @author foolish-bird
 */
public abstract class SignatureRequestBodyMethodProcessor extends AbstractMessageConverterMethodArgumentResolver {

    private static final Logger log = LoggerFactory.getLogger(SignatureRequestBodyMethodProcessor.class);

    /**
     * appId
     */
    private static final String APP_ID = "appId";

    /**
     * 签名字符串
     */
    private static final String SIGNATURE = "signature";

    /**
     * 求情时间
     */
    private static final String REQUEST_TIMESTAMP = "requestTimestamp";

    /**
     * 随机字符串
     */
    private static final String RANDOM_STRING = "randomString";

    public SignatureRequestBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
        super(converters);
    }

    public SignatureRequestBodyMethodProcessor(List<HttpMessageConverter<?>> converters, List<Object> requestResponseBodyAdvice) {
        super(converters, requestResponseBodyAdvice);
    }


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(SignatureRequestBody.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        parameter = parameter.nestedIfOptional();
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
            if (arg != null) {
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                }
            }
            if (mavContainer != null) {
                mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
            }
        }

        return adaptArgumentIfNecessary(arg, parameter);
    }

    @Override
    protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        boolean signature = false;
        try {
            signature = signature(inputMessage);
        } catch (Exception e) {
            log.error("签名认证失败：{}", e);
            throw new SignatureRuntimeException(e);
        }
        if (!signature) {
            throw new SignatureRuntimeException("签名认证失败");
        }

        Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
        if (arg == null && checkRequired(parameter)) {
            throw new HttpMessageNotReadableException("Required request body is missing: " +
                    parameter.getExecutable().toGenericString(), inputMessage);
        }
        return arg;
    }

    @Override
    protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");
        return new CacheServletServerHttpRequest(servletRequest);
    }

    /**
     * 签名认证
     *
     * @param request
     * @return
     */
    protected boolean signature(ServletServerHttpRequest request) throws Exception {
        CacheServletServerHttpRequest cache = (CacheServletServerHttpRequest) request;
        String requestTimestamp = cache.getServletRequest().getHeader(REQUEST_TIMESTAMP);
        String randomString = cache.getServletRequest().getHeader(RANDOM_STRING);
        String appId = cache.getServletRequest().getHeader(APP_ID);

        String signature = cache.getServletRequest().getHeader(SIGNATURE);
        byte[] body = cache.getBodyByte();

        String s = append(requestTimestamp, randomString, appId, new String(body, StandardCharsets.UTF_8));
        log.info("签名内容:{}", s);
        log.info("签名：{}", signature);

        return RSAUtils.verify(s.getBytes(StandardCharsets.UTF_8), signature, thirdPublicKey(appId), RSA_KEY_ALGORITHM);

    }

    protected boolean checkRequired(MethodParameter parameter) {
        SignatureRequestBody requestBody = parameter.getParameterAnnotation(SignatureRequestBody.class);
        return (requestBody != null && requestBody.required() && !parameter.isOptional());
    }

    /**
     * 查询三方公共密钥
     *
     * @param appId
     * @return
     */
    protected abstract byte[] thirdPublicKey(String appId);

    /**
     * 签名字符串拼接规则
     *
     * @param requestTimestamp
     * @param randomString
     * @param appId
     * @param body
     * @return
     */
    private String append(String requestTimestamp, String randomString, String appId, String body) {
        return requestTimestamp + "_" + randomString + "_" + appId + "_" + body;
    }
}
